import {Scene} from './Scene'
import {DeviceCaps} from "../../../src/gfx";
import {Shader} from "./Shader";
import {Cache} from "../../../src/ds/caches";

// import {ShaderLib} from "./ShaderLib";

export class App {

    private readonly _canvas: HTMLCanvasElement;
    private readonly _loop: (time: number) => void;
    private readonly _gl: WebGL2RenderingContext;
    _scene:Scene | null = null;
    _shaderCache:Cache<Shader> | null = null;

    static _app:App;

    //----------------------
    // debug
    private readonly _WEBGL_debug_renderer_info: WEBGL_debug_renderer_info | null = null;
    protected _renderer = '';
    protected _vendor = '';
    protected _version = '';
    protected _caps = new DeviceCaps();

    public static instance():App{
        return App._app
    }

    public constructor(
        public width: number,
        public height: number) {
        this._canvas = document.createElement('canvas');
        document.body.appendChild(this._canvas)

        this._canvas.width = width;
        this._canvas.height = height;
        this._loop = this.loop.bind(this);

        this._gl = this._canvas.getContext('webgl2',{
            depth:true,
            stencil:true,
            alpha:true,
            antialias:false,
            preserveDrawingBuffer:false
        }) as WebGL2RenderingContext;
        if (!this._gl) {
            throw new Error('can not get webgl2 context!');
        }
        App._app = this;


        //---------------------------------------------------
        let gl = this._gl;
        this._WEBGL_debug_renderer_info = this.getExtension('WEBGL_debug_renderer_info');
        if (this._WEBGL_debug_renderer_info) {
            this._renderer = gl.getParameter(this._WEBGL_debug_renderer_info.UNMASKED_RENDERER_WEBGL);
            this._vendor = gl.getParameter(this._WEBGL_debug_renderer_info.UNMASKED_VENDOR_WEBGL);
        } else {
            this._renderer = gl.getParameter(gl.RENDERER);
            this._vendor = gl.getParameter(gl.VENDOR);
        }

        this._version = gl.getParameter(gl.VERSION);
        this._caps.maxVertexAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
        this._caps.maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
        this._caps.maxFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
        this._caps.maxTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
        this._caps.maxVertexTextureUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
        this._caps.maxUniformBufferBindings = gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS);
        this._caps.maxUniformBlockSize = gl.getParameter(gl.MAX_UNIFORM_BLOCK_SIZE);
        this._caps.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
        this._caps.maxCubeMapTextureSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
        this._caps.uboOffsetAlignment = gl.getParameter(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT);
        this._caps.depthBits = gl.getParameter(gl.DEPTH_BITS);
        this._caps.stencilBits = gl.getParameter(gl.STENCIL_BITS);

        console.info('---------------------------------------------------');
        console.info(`RENDERER: ${this._renderer}`);
        console.info(`VENDOR: ${this._vendor}`);
        console.info(`VERSION: ${this._version}`);
        console.info(`SCREEN_SIZE: ${this.width} x ${this.height}`);
        console.info(`MAX_VERTEX_ATTRIBS: ${this._caps.maxVertexAttributes}`);
        console.info(`MAX_VERTEX_UNIFORM_VECTORS: ${this._caps.maxVertexUniformVectors}`);
        console.info(`MAX_FRAGMENT_UNIFORM_VECTORS: ${this._caps.maxFragmentUniformVectors}`);
        console.info(`MAX_TEXTURE_IMAGE_UNITS: ${this._caps.maxTextureUnits}`);
        console.info(`MAX_VERTEX_TEXTURE_IMAGE_UNITS: ${this._caps.maxVertexTextureUnits}`);
        console.info(`MAX_UNIFORM_BUFFER_BINDINGS: ${this._caps.maxUniformBufferBindings}`);
        console.info(`MAX_UNIFORM_BLOCK_SIZE: ${this._caps.maxUniformBlockSize}`);
        console.info(`DEPTH_BITS: ${this._caps.depthBits}`);
        console.info(`STENCIL_BITS: ${this._caps.stencilBits}`);
        console.info(`UNIFORM_BUFFER_OFFSET_ALIGNMENT: ${this._caps.uboOffsetAlignment}`);
        console.info('---------------------------------------------------');

        this.initStates(gl);
        // ShaderLib.init();
    }

    get canvas() {
        return this._canvas;
    }

    get gl():WebGL2RenderingContext {
        return this._gl;
    }

    startWithScene(scene:Scene) {
        this._scene = scene;
        this._scene.init();
        requestAnimationFrame(this._loop);
    }

    loop() {
        requestAnimationFrame(this._loop);
        if (this._scene){
            this._scene.frame();
        }
    }

    private getExtension (ext: string): any {
        const prefixes = ['', 'WEBKIT_', 'MOZ_'];
        for (let i = 0; i < prefixes.length; ++i) {
            const _ext = this.gl.getExtension(prefixes[i] + ext);
            if (_ext) {
                // eslint-disable-next-line @typescript-eslint/no-unsafe-return
                return _ext;
            }
        }
        return null;
    }

    private initStates (gl: WebGL2RenderingContext) {
        gl.activeTexture(gl.TEXTURE0);
        gl.pixelStorei(gl.PACK_ALIGNMENT, 1);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        // rasterizer state
        gl.enable(gl.SCISSOR_TEST);
        gl.enable(gl.CULL_FACE);
        gl.cullFace(gl.BACK);
        gl.frontFace(gl.CCW);
        gl.polygonOffset(0.0, 0.0);

        // depth stencil state
        gl.enable(gl.DEPTH_TEST);
        gl.depthMask(true);
        gl.depthFunc(gl.LESS);

        gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, 1, 0xffff);
        gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.KEEP);
        gl.stencilMaskSeparate(gl.FRONT, 0xffff);
        gl.stencilFuncSeparate(gl.BACK, gl.ALWAYS, 1, 0xffff);
        gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.KEEP);
        gl.stencilMaskSeparate(gl.BACK, 0xffff);

        gl.disable(gl.STENCIL_TEST);

        // blend state
        gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
        gl.disable(gl.BLEND);
        gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
        gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ONE, gl.ZERO);
        gl.colorMask(true, true, true, true);
        gl.blendColor(0.0, 0.0, 0.0, 0.0);
    }

    set shaderCache(cache:Cache<Shader>){
        this._shaderCache = cache;
    }

    get shaderCache(){
        return this._shaderCache!!;
    }
}
